Skip to content

Fastapi conversion#2071

Merged
bfoley12 merged 207 commits into
devfrom
fastapi-conversion
Jul 15, 2026
Merged

Fastapi conversion#2071
bfoley12 merged 207 commits into
devfrom
fastapi-conversion

Conversation

@bfoley12

@bfoley12 bfoley12 commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator

Summary

Start of major rewrite of the API server to FastAPI and other modern frameworks (pydantic, Beanie, etc.)

This PR is the essential groundwork of the rewrite. However, further PRs are necessary for full functionality.

Major TODOs after this PR

  • Downloads
    • This involves infra design to push data from Contributions and Components into S3 as parquet (maybe delta tables if it goes that far)
  • Unit normalization (Pint/Unyt)
    • Also entails making input data formatted strings, then internally parsing the formats into better representations (objects) before storage
  • Greatly expand telemetry

Minor TODOs after this PR:

  • Add indices to Beanie models
  • Remove notebooks
  • Remove CIF
  • Email notifications

@bfoley12 bfoley12 self-assigned this Jun 16, 2026
@bfoley12 bfoley12 added this to the API Server Rewrite milestone Jun 16, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing jupyter and local datadog reference

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing jupyter and local datadog reference

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ruff formatting

Comment thread mpcontribs-api/gunicorn.conf.py Outdated

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fastapi uses uvicorn

Comment thread mpcontribs-api/justfile

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Convenient command runner

Comment thread mpcontribs-api/Dockerfile

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switch from pip building to uv.
!! Needs further verification

Comment thread mpcontribs-api/main.py

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ruff formatting

Comment thread mpcontribs-api/maintenance.py Outdated

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not using MongoEngine.
!! Still need to implement unit checks, but will be outside of this scope

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Upgrade python to 3.14; move to uv, ruff, and basedpyright; removed outdated/unnecessary packages

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the base stack is still at python 3.11

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I have a note in the Dockerfile about requiring a 3.14 image build - I assumed it would be a good time to bump our python version and use up to date package versions.
Do you think this should be in the scope of the rewrite?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getting to 3.12 would be a good start for the base stack

@bfoley12
bfoley12 force-pushed the fastapi-conversion branch from 8dade26 to fa4886c Compare June 17, 2026 20:50

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A lot of this logic is used to handle parsing dotted paths (data.x.y.z)

Comment thread mpcontribs-api/Dockerfile
Comment on lines -25 to -26
wget -q https://github.com/materialsproject/MPContribs/blob/master/mpcontribs-api/mpcontribs/api/contributions/formulae.json.gz?raw=true \
-O mpcontribs/api/contributions/formulae.json.gz

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reason for removing the formulae file?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file seemed to just function as an id-to-formula translation. I made formulas required when POSTing.

If we want that moved back to optional and filled in automatically, I think I'd prefer actively fetching the formula over keeping it in a data file that we track in git. That would mean an extra query to mongo, and reaching into the nextgen database, but I feel that it is more reactive and resilient that way.

Comment thread mpcontribs-api/Dockerfile
FROM materialsproject/devops:python-3.1113.4 AS base
RUN apt-get update && apt-get install -y --no-install-recommends supervisor libopenblas-dev libpq-dev vim && apt-get clean
# FROM materialsproject/devops:python-3.1113.4 AS base
FROM python:3.14-slim AS base

@bfoley12 bfoley12 Jun 18, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved off of the shared base image here. There are definitely both pros and cons (below), and I have to test a full build still.
Pros:

  1. UV uses a lockfile and doesn't need to rely on manually finding and building binaries - this seems to be the main benefit of the shared image (scipy, matplotlib, etc.)
  2. Allows each portion of the stack to upgrade python independently (we don't have to hold back one piece until we build a shared image and force every upgrade at once)

Cons:

  1. If we have truly shared logic, we will have duplication, or revert to a shared image
  2. If the underlying default image changes, we would silently change as well

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image size is the core reason to start from the shared base image

elif isinstance(value, list):
for item in value:
_validate_nested_keys(item)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left as separate function in case we want to expand the validation to multiple steps.

@tsmathis tsmathis left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Frankly this PR is too big to give a meaningful review across the entire thing without having some hands on experience. I roughly "get it", but I'll likely have a better handle on things once we go through trying to deploy this.

One overall thing that isn't quite clear to me is how I would extend/consume the underlying framework you have to start replacing the emmet-api. What would I need to do to replace the summary endpoint for example?

Is there some "core abstraction" running through this that I/we could build from to replace emmet-api's internals?

Comment thread mpcontribs-api/src/mpcontribs_api/domains/_shared/bulk.py Outdated
Comment thread mpcontribs-api/src/mpcontribs_api/domains/_shared/repository.py
Comment thread mpcontribs-api/src/mpcontribs_api/domains/_shared/types.py
from mpcontribs_api.domains._shared.models import Component, ComponentIn, DocumentOut
from mpcontribs_api.domains._shared.types import MD5Hash, PolarsFrame
from mpcontribs_api.projection import SparseFieldsModel

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pretty much everything in this file will have a naming collision with a pymatgen class

should just reuse them

Comment on lines +87 to +92
class StructurePatch(SparseFieldsModel):
name: str | None = None
lattice: Lattice | None = None
sites: list[Site] | None = None
charge: float | None = None
cif: str | None = None

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

patching individual parts of a structure should be a no-go

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't know that, but makes sense!

Added model_validator requiring all of {lattice, sites, charge, cif} be specified in a patch if one of them is. Additionally, I only allow charge to be None

Comment on lines +76 to +85
# AuthedDep = Annotated[User, Depends(require_user)]


# def require_role(role: str):
# def checker(user: AuthedDep) -> User:
# if not user.has_role(role):
# raise PermissionError(required_role=role)
# return user

# return Annotated[User, Depends(checker)]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These aren't actually being used. Removed them.

We handle role authorization at the data level, not the route level, so we end up working directly with the user object (which can tell us directly whether it can work with an object) rather than require_role. AuthedDep is not needed by how I constructed the use of User objects in repos and services. I construct a repo/service with a user at request time, so it is immediately scoped to the user - the route doesn't need to user object, just the repo/service.

Comment thread mpcontribs-api/Dockerfile
FROM materialsproject/devops:python-3.1113.4 AS base
RUN apt-get update && apt-get install -y --no-install-recommends supervisor libopenblas-dev libpq-dev vim && apt-get clean
# FROM materialsproject/devops:python-3.1113.4 AS base
FROM python:3.14-slim AS base

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image size is the core reason to start from the shared base image

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getting to 3.12 would be a good start for the base stack

@bfoley12

Copy link
Copy Markdown
Collaborator Author

Frankly this PR is too big to give a meaningful review across the entire thing without having some hands on experience. I roughly "get it", but I'll likely have a better handle on things once we go through trying to deploy this.

One overall thing that isn't quite clear to me is how I would extend/consume the underlying framework you have to start replacing the emmet-api. What would I need to do to replace the summary endpoint for example?

Is there some "core abstraction" running through this that I/we could build from to replace emmet-api's internals?

Yeah, it's quite large...

For merging, it would be primarily the server architecture. We would make a "shared kernel" package of the "base" files (app, authz, dependencies, etc.) and make them more general (ie. parameterize the app factory). Then include the Resource.routers into the new app framework, use improved error handling, and switch our logging. We would also consolidate our data access logic with repositories (one class for MPContribs, one Read-Only class for emmet), pagination, and use fastapi-filter.

Sharing the domain logic (our ODM, repos, services, models) is more difficult by nature. emmet is many read-only collections, so it makes sense to have factories/registries to handle efficient creation, while MPContribs collections have different behaviors.

The core abstraction is Router -> Filter -> (service) -> Repository(scope, pagination) -> SparseFieldsModel (projects/ is a good example). We'd shift emmet like: QueryOperators -> Filter, SparseFieldsQuery -> SparseFieldsModel, GlobalHeaderProcessor -> scope, PaginationQuery -> Page+CursorParams. This isn't directly possible today - we need to define a ReadOnlyRepository that doesn't use Beanie and to create the "shared kernel".

Addressing the Summary endpoint: most of the declaration would live in SummaryFilter (we'd include all fields, and most likely have to generate the __gte, __lte, etc. fields), and header_processor and query_to_configure_on_request would be removed in favor of scope. I'd also most likely want to move towards a simpler registry pattern - ideally just define a Filter and add a line to the registry.

@tsmathis

Copy link
Copy Markdown
Collaborator

For merging, it would be primarily the server architecture. We would make a "shared kernel" package of the "base" files (app, authz, dependencies, etc.) and make them more general (ie. parameterize the app factory). Then include the Resource.routers into the new app framework, use improved error handling, and switch our logging. We would also consolidate our data access logic with repositories (one class for MPContribs, one Read-Only class for emmet), pagination, and use fastapi-filter.

yep this would be great to work towards

Sharing the domain logic (our ODM, repos, services, models) is more difficult by nature. emmet is many read-only collections, so it makes sense to have factories/registries to handle efficient creation, while MPContribs collections have different behaviors.

Intuition says that at least the bones of the api logic should/will be sharable, and then yes the individual 'front ends' (emmet/contribs) would have to handle defining their respective models, etc.

ReadOnlyRepository that doesn't use Beanie

any particular reason we can't use Beanie for everything that touches mongo?

@bfoley12

Copy link
Copy Markdown
Collaborator Author

Sharing the domain logic (our ODM, repos, services, models) is more difficult by nature. emmet is many read-only collections, so it makes sense to have factories/registries to handle efficient creation, while MPContribs collections have different behaviors.

Intuition says that at least the bones of the api logic should/will be sharable, and then yes the individual 'front ends' (emmet/contribs) would have to handle defining their respective models, etc.

Definitely can be, but it depends how much boilerplate we want to manage. Ie. Adding a folder that contains a router.py, repo.py, and models.py for a few GET routes (no POST/PUT/DELETE/PATCH) that utilize a single model - not complex, but we could *probably* get the same done with fewer LOC by generating in a similar way to how we do it now.

ReadOnlyRepository that doesn't use Beanie

any particular reason we can't use Beanie for everything that touches mongo?

100% can. It would unify how we interact with models, just again turns into a tradeoff of the extra boilerplate being worth it. It might end up reducing code elsewhere, so could be a win regardless.

bfoley12 added 26 commits July 14, 2026 16:56
…verwrite the requested fields and add in null values for the non-requested fields
…from the number of documents sharing (project, identifier) in projects where unique_identifiers=False, otherwise version === 1
…nd only allows charge to be None. Added tests for it
@bfoley12
bfoley12 force-pushed the fastapi-conversion branch from 346d4c9 to 1b72f07 Compare July 15, 2026 00:00
@bfoley12
bfoley12 marked this pull request as ready for review July 15, 2026 00:03
@bfoley12
bfoley12 merged commit a9af194 into dev Jul 15, 2026
@bfoley12
bfoley12 deleted the fastapi-conversion branch July 15, 2026 00:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants